﻿using AnnealingModel.Demo.Common.Function;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AnnealingModel.Demo.Common.Algorithm
{
    internal class Annealing
    {
        private readonly Random _random = new Random();
        public Annealing()
        {

        }
        public double T { get; set; } = 2000;
        public double Dt { get; set; } = 0.99d;
        public double Eps { get; set; } = 0.0001d;
        public FuntionBase Funtion { get; set; }
        public int Time { get; set; } = 30;
        public int InnerLoop { get; set; } = 1000;
        public event Action<double, double>? ValueCallback;
        public void Run()
        {
            double bestX = GenerateRandomNumberInRange(_random, Funtion.Range.lower, Funtion.Range.upper, Funtion.Accuracy);
            double bestY = Fitness(bestX);
            ValueCallback?.Invoke(bestX, bestY);
            while (T > Eps)
            {
                for (int i = 0; i < InnerLoop; i++)
                {
                    double dx = GenerateNeighborhood(_random, bestX, Funtion.Range.lower, Funtion.Range.upper, T);
                    while (dx > Funtion.Range.upper || dx < Funtion.Range.lower)
                        dx = GenerateNeighborhood(_random, dx, Funtion.Range.lower, Funtion.Range.upper, T);
                    double df = Fitness(dx);
                    Funtion.Select.Select(_random, T, ref dx, ref df, ref bestX, ref bestY);
                }
                ValueCallback?.Invoke(bestX, bestY);
                T *= Dt;
                Thread.Sleep(0);
            }
        }
        private double GenerateNeighborhood(Random random, double current, double lower, double upper, double temperature)
        {
            double step = GenerateRandomNumberInRange(random,-1,1,Funtion.Accuracy) * temperature; // 调整步长
            double next = current + step;
            if(next <= upper && next>=lower)
            {
            }
            else if(next<lower)
            {
                var rand = GenerateRandomNumberInRange(random, -1, 1, Funtion.Accuracy);
                next = rand * lower + (1 - rand) * current;
            }
            else
            {
                var rand = GenerateRandomNumberInRange(random, -1, 1, Funtion.Accuracy);
                next=rand * upper + (1 - rand)*current;
            }
            return next;
        }
        private double Fitness(params double[] xs)=> Funtion.Func(xs);
        private double GenerateRandomNumberInRange(Random random, double min, double max, int decimalPlaces)
        {
            double randomNumber = min + (max - min) * random.NextDouble();
            double multiplier = Math.Pow(10, decimalPlaces);
            return Math.Round(randomNumber * multiplier) / multiplier;
        }
    }
}
